#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "macros.h"
#include "buf.h"
#include "buf-private.h"

int
buf_ensure_capacity(buf_t *buf, size_t min_capacity) {
  buf_elem_t *prev_elems, *next_elems;
  size_t capacity;

  capacity = buf->capacity;
  if (unlikely(min_capacity > capacity)) {
    prev_elems = buf->buf;
    capacity = min_capacity * 2;
    next_elems = realloc(prev_elems, capacity);
    if (unlikely(next_elems == NULL)) {
      return BUF_NOMEM;
    }
    buf->buf = next_elems;
    buf->capacity = capacity;
  }
  return 0;
}

int
buf_encode_binary(buf_t *buf, void const *p, size_t len) {
  int result;
  size_t size, new_size;

  size = buf->size;
  new_size = size + len;
  if (unlikely((result = buf_ensure_capacity(buf, new_size)))) {
    return result;
  }
  memcpy(buf->buf + size, p, len);
  buf->size = new_size;
  return 0;
}

int
buf_encode_uint8(buf_t *buf, uint8_t elem) {
  int result;
  size_t size, new_size;

  size = buf->size;
  new_size = size + 1;
  if (unlikely((result = buf_ensure_capacity(buf, new_size)))) {
    return result;
  }
  *(buf->buf + size) = elem;
  buf->size = new_size;
  return 0;
}

int
buf_encode_uint16(buf_t *buf, uint16_t u16) {
  int result;
  size_t size, new_size;
  buf_elem_t *elems;

  size = buf->size;
  new_size = size + 2;
  if (unlikely((result = buf_ensure_capacity(buf, new_size)))) {
    return result;
  }
  elems = buf->buf + size;
  elems[0] = u16;
  elems[1] = u16 >> 8;
  buf->size = new_size;
  return 0;
}

int
buf_encode_int32(buf_t *buf, int32_t i32) {
  size_t size, new_size;
  int result;
  buf_elem_t *elems, *elem;

  size = buf->size;
  new_size = size + 4;
  if (unlikely((result = buf_ensure_capacity(buf, new_size)))) {
    return result;
  }
  elems = buf->buf + size;
  elem = (buf_elem_t *) &i32;
  elems[0] = elem[0];
  elems[1] = elem[1];
  elems[2] = elem[2];
  elems[3] = elem[3];
  buf->size = new_size;
  return 0;
}

int
buf_encode_double(buf_t *buf, double d) {
  size_t size, new_size;
  int result;
  buf_elem_t *elems, *elem;

  size = buf->size;
  new_size = size + 8;
  if (unlikely((result = buf_ensure_capacity(buf, new_size)))) {
    return result;
  }
  elems = buf->buf + size;
  elem = (buf_elem_t *) &d;
  elems[0] = elem[0];
  elems[1] = elem[1];
  elems[2] = elem[2];
  elems[3] = elem[3];
  elems[4] = elem[4];
  elems[5] = elem[5];
  elems[6] = elem[6];
  elems[7] = elem[7];
  buf->size = new_size;
  return 0;
}

int
buf_encode_printf(buf_t *buf, char const *format, ...) {
  va_list args;
  int size, new_size;
  int result;

  va_start(args, format);
  size = vsnprintf((char *) buf->buf, 0, format, args);
  va_end(args);
  if (unlikely(size < 0)) {
    return BUF_PRINTF;
  }
  new_size = buf->size + size;
  if (unlikely((result = buf_ensure_capacity(buf, new_size + 1)))) {
    return result;
  }
  va_start(args, format);
  size = vsnprintf((char *) buf->buf + buf->size, size + 1, format, args);
  va_end(args);
  if (unlikely(size < 0)) {
    return BUF_PRINTF;
  }
  buf->size = new_size;
  return 0;
}
